package service

import (
	"context"
	"database/sql"
	"strings"

	dbmodel "gitee.com/hp-huiw/family-expense-api/internal/db/model"
	"gitee.com/hp-huiw/family-expense-api/internal/dto"
	"gitee.com/hp-huiw/family-expense-api/internal/repository"
	"gitee.com/hp-huiw/my-go-core/date"
	"gitee.com/hp-huiw/my-go-core/util"
	"github.com/go-jet/jet/v2/qrm"
	"github.com/samber/lo"
)

type JoinAccountGroupRequestService interface {
	CountReceived(ctx context.Context, receiverUserID string) (int64, error)
	QueryReceiving(ctx context.Context, receiverUserID string, pageSize int64, pageIndex int64) ([]dto.JoinAccountGroupReceivingResponse, error)
	AddOrUpdate(ctx context.Context, accountGroupID string, senderID string, receiverID string, roles []string) error
	Accept(ctx context.Context, id string, receiverID string) error
	Reject(ctx context.Context, id string, receiverID string) error
	DeleteWithTx(ctx context.Context, tx *sql.Tx, accountGroupID string, userID string) error
}

func NewJoinAccountGroupRequestService(db *sql.DB) JoinAccountGroupRequestService {
	return &joinAccountGroupRequestService{
		db:                                db,
		joinAccountGroupRequestRepository: repository.NewJoinAccountGroupRequestRepository(db),
	}
}

// Counts received requests by receiver user id.
func (s *joinAccountGroupRequestService) CountReceived(ctx context.Context, receiverUserID string) (int64, error) {
	return s.joinAccountGroupRequestRepository.CountReceived(ctx, receiverUserID)
}

// Queries pending requests for receiver.
func (s *joinAccountGroupRequestService) QueryReceiving(ctx context.Context, receiverUserID string, pageSize int64, pageIndex int64) ([]dto.JoinAccountGroupReceivingResponse, error) {
	query, err := s.joinAccountGroupRequestRepository.QueryReceiving(ctx, receiverUserID, pageSize, pageIndex)

	if err != nil {
		return nil, err
	}

	userService := NewUserService(nil)

	result := lo.Map(query, func(item repository.JoinAccountGroupReceiving, _ int) dto.JoinAccountGroupReceivingResponse {
		avatarUrl := userService.GetAvatarUrl(&item.OwnerAvatarFilename)

		return dto.JoinAccountGroupReceivingResponse{
			ID: item.ID,
			Owner: dto.JoiningRequestor{
				Username:  item.OwnerUsername,
				FullName:  item.OwnerFullName,
				AvatarUrl: &avatarUrl,
			},
			Sender: dto.JoiningRequestor{
				Username: item.SenderUsername,
				FullName: item.SenderFullName,
			},
			LastUpdated: item.LastUpdated,
		}
	})

	return result, nil
}

// Adds new request or updates existing one.
func (s *joinAccountGroupRequestService) AddOrUpdate(ctx context.Context, accountGroupID string, senderID string, receiverID string, roles []string) error {
	exists, err := s.joinAccountGroupRequestRepository.Exists(ctx, accountGroupID, senderID, receiverID)

	if err != nil {
		return err
	}

	var ownerID string

	if !exists {
		ownerID, err = NewAccountGroupUserService(s.db).QueryCreatorID(ctx, accountGroupID)

		if err != nil {
			return err
		}

		model := dbmodel.JoinAccountGroupRequest{
			ID:             util.GenerateUUIDWithoutDashes(),
			AccountGroupID: accountGroupID,
			OwnerID:        ownerID,
			SenderID:       senderID,
			ReceiverID:     receiverID,
			Roles:          strings.Join(roles, ","),
			LastUpdated:    date.GetCurrentDate(),
		}

		err = s.joinAccountGroupRequestRepository.Insert(ctx, &model)
	} else {
		// Updates roles just in case it has been changed.
		err = s.joinAccountGroupRequestRepository.Update(ctx, accountGroupID, senderID, receiverID, roles)
	}

	return err
}

// Accept is request receiver accepting to this request.
func (s *joinAccountGroupRequestService) Accept(ctx context.Context, id string, receiverID string) error {
	data, err := s.joinAccountGroupRequestRepository.QueryByReceiver(ctx, id, receiverID)

	if err != nil {
		return err
	}

	if data != nil {
		tx, err := s.db.Begin()

		if err != nil {
			return err
		}

		defer tx.Rollback()

		roleIDs := strings.Split(data.Roles, ",")

		// Saves relationship between user and account group.
		groupUser := dbmodel.AccountGroupUser{
			AccountGroupID:        data.AccountGroupID,
			UserID:                data.ReceiverID,
			IsAccountGroupCreator: false,
		}

		if err = NewAccountGroupUserService(s.db).InsertWithTx(ctx, tx, &groupUser); err != nil {
			return err
		}

		// Saves all user roles.
		userRoles := lo.Map(roleIDs, func(item string, _ int) dbmodel.UserRole {
			return dbmodel.UserRole{
				AccountGroupID: data.AccountGroupID,
				UserID:         data.ReceiverID,
				RoleID:         item,
			}
		})

		if err = NewUserRoleService(s.db).BatchInsertWithTx(ctx, tx, userRoles); err != nil {
			return err
		}

		// Deletes the completed request.
		s.deleteReceiving(ctx, tx, id, receiverID)

		return tx.Commit()
	}

	return nil
}

// Reject is request receiver rejecting to this request.
func (s *joinAccountGroupRequestService) Reject(ctx context.Context, id string, receiverID string) error {
	return s.deleteReceiving(ctx, s.db, id, receiverID)
}

// DeleteWithTx deletes all join account group requests by account group id or user id with transaction.
func (s *joinAccountGroupRequestService) DeleteWithTx(ctx context.Context, tx *sql.Tx, accountGroupID string, userID string) error {
	return s.joinAccountGroupRequestRepository.DeleteWithTx(ctx, tx, accountGroupID, userID)
}

// Deletes request based on its id and receiver id.
func (s *joinAccountGroupRequestService) deleteReceiving(ctx context.Context, db qrm.DB, id string, receiverID string) error {
	return s.joinAccountGroupRequestRepository.DeleteByReceiver(ctx, db, id, receiverID)
}

// Container of JoinAccountGroupRequestService functions.
type joinAccountGroupRequestService struct {
	db                                *sql.DB
	joinAccountGroupRequestRepository repository.JoinAccountGroupRequestRepository
}
